在上一篇文章中,我們使用餐廳點餐的方式認識了REST API,也了解一次API溝通通常包含:
今天要進一步認識HTTP中常見的四種方法:
在FHIR RESTful API中,它們可以用來讀取、搜尋、新增、更新及刪除Resource。
不過,四種方法不只名稱不同,它們使用的URL、Request Body、成功狀態碼,以及重複送出後的結果也可能不同。
本文使用的網址、病人及醫療資料皆為虛構教學範例。實際操作時,請勿將真實病人資料傳送至公開測試伺服器。
假設FHIR Server中保存一筆Patient Resource。
我們可能會想執行以下操作:
| 需求 | HTTP方法 |
|---|---|
| 讀取一筆Patient | GET |
| 搜尋Patient | GET |
| 建立新的Patient | POST |
| 更新既有Patient | PUT |
| 刪除Patient | DELETE |
可以先用一句話記住:
GET讀取、POST新增、PUT更新、DELETE刪除。
但實際規則比這句話更完整,接下來分別介紹。
GET用來向Server取得資料。
在FHIR中,GET常用於:
GET通常不使用Request Body,而是透過URL及查詢參數表達需求。
如果已經知道Patient的id,可以送出:
GET https://hospital.example.org/fhir/Patient/patient-001
Accept: application/fhir+json
URL可以拆成:
| 部分 | 內容 |
|---|---|
| FHIR Server | https://hospital.example.org/fhir |
| Resource類型 | Patient |
| Resource id | patient-001 |
整個Request代表:
請讀取id為patient-001的Patient Resource,並回傳FHIR JSON。
如果資料存在,Server可能回傳:
200 OK
Content-Type: application/fhir+json
Response Body:
{
"resourceType": "Patient",
"id": "patient-001",
"active": true,
"name": [
{
"text": "王小明"
}
]
}
如果不知道Resource id,但知道病人姓名,可以使用搜尋:
GET https://hospital.example.org/fhir/Patient?name=王小明
Accept: application/fhir+json
其中:
?name=王小明
是搜尋參數。
搜尋結果通常不是直接回傳單一Patient,而是回傳Bundle Resource。
簡化範例如下:
{
"resourceType": "Bundle",
"type": "searchset",
"total": 1,
"entry": [
{
"resource": {
"resourceType": "Patient",
"id": "patient-001",
"name": [
{
"text": "王小明"
}
]
}
}
]
}
即使只找到一筆資料,FHIR搜尋結果仍通常會以Bundle呈現。Bundle會在Day 19詳細介紹。
正常的GET操作是用來讀取資料,不應因為執行GET而修改Resource。
例如,重複送出:
GET /Patient/patient-001
應該只是重複取得Patient,而不是建立新Patient或改變病人姓名。
這種不應改變Server資源狀態的HTTP方法,稱為Safe Method。
不過,Server仍可能留下存取Log、統計次數或更新快取,這些技術性紀錄不等於修改Client要求讀取的Patient內容。
POST在FHIR中常用來建立新的Resource。
例如,要建立一筆Patient,可以送出:
POST https://hospital.example.org/fhir/Patient
Content-Type: application/fhir+json
Accept: application/fhir+json
Request Body:
{
"resourceType": "Patient",
"active": true,
"name": [
{
"text": "王小明",
"family": "王",
"given": [
"小明"
]
}
],
"gender": "male",
"birthDate": "2000-01-01"
}
注意POST建立Resource時,URL通常停在Resource類型:
/Patient
而不是:
/Patient/patient-001
這是因為一般FHIR create操作會由Server分配新Resource的id。
如果Patient建立成功,Server通常會回傳:
201 Created
Response Header可能包含:
Location: https://hospital.example.org/fhir/Patient/123/_history/1
這表示Server建立了一筆id為123的Patient,目前版本為1。
依照Server的回應設定,Response Body也可能包含剛建立的Patient:
{
"resourceType": "Patient",
"id": "123",
"meta": {
"versionId": "1"
},
"active": true,
"name": [
{
"text": "王小明"
}
]
}
Client應該保存Server回傳的id,後續才能讀取或更新該Resource。
一般FHIR create操作中:
/Patient
所以Request Body通常不應依賴Client自行指定的id。
即使Client傳送了某個id,Server也可能忽略或拒絕它,實際行為應依照FHIR規範及該Server的實作。
如果Client需要對指定id的位置進行更新或建立,通常會使用PUT,而不是一般POST create。
假設將相同的POST Request送出兩次:
POST /Patient
Server可能建立兩筆不同的Patient:
Patient/123
Patient/124
即使Request Body完全相同,Server也不一定知道這是重送,還是真的要建立兩筆資料。
因此,POST一般不是Idempotent Method。
Idempotent可以理解為:
將相同Request執行一次或多次,預期Server的最終資源狀態相同。
一般POST create重複執行可能建立多筆Resource,所以操作時要小心網路重送及重複建檔問題。
FHIR也提供Conditional Create等機制協助避免重複建立,但屬於較進階內容。
PUT在FHIR中常用來更新指定id的Resource。
例如,要更新:
Patient/patient-001
可以送出:
PUT https://hospital.example.org/fhir/Patient/patient-001
Content-Type: application/fhir+json
Accept: application/fhir+json
Request Body:
{
"resourceType": "Patient",
"id": "patient-001",
"active": true,
"name": [
{
"text": "王小明",
"family": "王",
"given": [
"小明"
]
}
],
"gender": "male",
"birthDate": "2000-01-01",
"telecom": [
{
"system": "phone",
"value": "0900-000-001",
"use": "mobile"
}
]
}
這個Request代表:
將Patient/patient-001更新為Request Body所提供的內容。
初學時可能會認為,如果只想修改電話,就只需要送出:
{
"telecom": [
{
"system": "phone",
"value": "0900-000-001"
}
]
}
但FHIR的update通常會將Request Body視為Resource的新內容,而不是只修改其中一個欄位。
如果省略原本的姓名、生日或其他資料,這些欄位可能從更新後的Resource中消失,或Request可能因資料不完整而失敗。
因此,使用PUT更新前,常見流程是:
如果只想修改部分欄位,HTTP另有PATCH方法,但FHIR Server不一定支援,格式也需要依照Server能力及規範使用。
如果更新既有Resource成功,Server通常可能回傳:
200 OK
或在沒有回傳內容時使用:
204 No Content
Server也可能更新Resource版本:
"meta": {
"versionId": "2"
}
如果Server允許使用PUT在指定位置建立原本不存在的Resource,成功時可能回傳:
201 Created
不過,不是每台FHIR Server都允許這種行為。
假設Request URL是:
/Patient/patient-001
那麼Request Body中的id也應該是:
"id": "patient-001"
如果URL指定的是patient-001,Body卻寫成:
"id": "patient-002"
Server通常應該拒絕這項不一致的Request。
Resource類型也必須正確。傳送到:
/Patient/patient-001
的Body不能是:
{
"resourceType": "Observation"
}
如果將完全相同的PUT Request重複傳送:
PUT /Patient/patient-001
預期最終的Resource內容仍然相同,不會像一般POST create一樣,每次都建立新的Patient。
Server可能留下不同的歷史版本或操作紀錄,但目標Resource的最終內容應保持一致。
因此,PUT被視為Idempotent Method。
DELETE用來要求Server刪除指定Resource。
例如:
DELETE https://hospital.example.org/fhir/Patient/patient-001
這個Request代表:
請刪除Patient/patient-001。
DELETE通常不需要Request Body。
如果刪除成功,Server可能回傳:
200 OK
或:
204 No Content
實際回應會依FHIR Server而異。
不一定。
FHIR Server可能採用不同的刪除方式,例如:
刪除後再次讀取:
GET /Patient/patient-001
可能收到:
404 Not Found
或:
410 Gone
兩者概念不同:
404 Not Found:Server找不到目前的Resource。410 Gone:Server知道Resource曾經存在,但目前已被刪除。不是所有Server都會使用完全相同的回應方式。
第一次執行:
DELETE /Patient/patient-001
Resource被刪除。
第二次執行相同Request時,Server可能回傳404或410,因為Resource已不存在。
雖然兩次Response的狀態碼可能不同,但Server中的最終資源狀態都一樣:
Patient/patient-001處於已刪除或無法取得的狀態。
因此,DELETE也被視為Idempotent Method。
| 項目 | GET | POST | PUT | DELETE |
|---|---|---|---|---|
| 主要用途 | 讀取或搜尋 | 建立Resource | 更新指定Resource | 刪除Resource |
| 常見URL | /Patient/123 |
/Patient |
/Patient/123 |
/Patient/123 |
| 通常有Request Body嗎? | 沒有 | 有 | 有 | 通常沒有 |
| 誰決定id? | 已知id | 通常由Server分配 | URL指定id | URL指定id |
| 是否修改Resource? | 否 | 是 | 是 | 是 |
| 是否Idempotent? | 是 | 一般create不是 | 是 | 是 |
| 常見成功碼 | 200 | 201 | 200、201或204 | 200或204 |
Status Code由三位數字組成,可以先依照第一個數字分類:
| 範圍 | 類別 |
|---|---|
| 1xx | 資訊回應 |
| 2xx | Request成功 |
| 3xx | 重新導向 |
| 4xx | Client端Request問題 |
| 5xx | Server端問題 |
FHIR API操作中,較常遇到2xx、4xx及5xx。
表示Request成功。
常見情境:
200 OK
表示新的Resource建立成功。
常見情境:
201 Created
通常可以從Location Header得知新Resource的位置。
表示Request成功,但Response沒有Body。
例如,更新或刪除成功後,Server可能只回傳:
204 No Content
看到空白Body不一定代表失敗,還要一起查看Status Code。
表示Request有問題,Server無法依照內容處理。
可能原因:
400 Bad Request
通常表示Client尚未提供有效的身分驗證資訊。
可能原因:
401 Unauthorized
雖然英文是Unauthorized,但實務上通常與「尚未完成有效身分驗證」有關。
表示Server已經知道Client的身分,但Client沒有執行該操作的權限。
例如:
403 Forbidden
可以簡化區分:
| 狀態碼 | 基本概念 |
|---|---|
| 401 | 尚未通過有效身分驗證 |
| 403 | 已辨識身分,但權限不足 |
表示找不到指定Resource或Endpoint。
例如:
GET /Patient/not-exist
可能回傳:
404 Not Found
可能原因包括:
表示URL可能存在,但不允許使用目前的HTTP方法。
例如,FHIR Server允許讀取Patient,卻不允許刪除:
DELETE /Patient/patient-001
可能回傳:
405 Method Not Allowed
表示Request和Server目前狀態發生衝突。
例如:
409 Conflict
表示Server能理解Request格式,但內容無法通過處理或驗證。
例如:
422 Unprocessable Entity
不同FHIR Server對400和422的使用方式可能略有差異,需要查看Response Body及Server文件。
表示Server處理Request時發生未預期錯誤。
500 Internal Server Error
這不一定代表Client完全沒有問題,但主要表示Server無法正常完成處理。
如果持續發生,通常需要查看Server Log或聯絡系統管理人員。
FHIR Server遇到錯誤時,可能回傳OperationOutcome Resource,提供較詳細的問題資訊。
例如:
{
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "error",
"code": "invalid",
"diagnostics": "Patient.id does not match the id in the request URL."
}
]
}
常見欄位包括:
| 欄位 | 用途 |
|---|---|
severity |
問題嚴重程度 |
code |
問題類型 |
details |
問題說明 |
diagnostics |
診斷或技術訊息 |
location或expression |
發生問題的位置 |
看到錯誤時,不應只看Status Code,也要閱讀OperationOutcome中的內容。
例如,同樣是400 Bad Request,真正原因可能是:
OperationOutcome可以提供更明確的線索。
不是每一台FHIR Server都支援:
可以先呼叫:
GET /metadata
取得CapabilityStatement,查看Server提供的能力。
因此,某個Request失敗不一定代表HTTP方法寫錯,也可能是該Server沒有開放這項功能。
POST、PUT及DELETE都可能改變Server中的資料,操作前要特別注意:
本系列實作只會使用公開測試環境及虛構資料。
請判斷以下需求應該使用哪一種HTTP方法。
GET /Patient/123
答案是GET。
POST /Patient
答案是POST。
PUT /Patient/123
答案是PUT。
DELETE /Patient/123
答案是DELETE。
GET /Patient?name=王小明
答案仍然是GET,因為這是讀取及搜尋資料。
今天認識了FHIR RESTful API中四種常見HTTP方法:
也認識了常見狀態碼,包括:
我認為今天最重要的觀念是:
Request完成後,不能只看Response Body有沒有資料,也要一起查看Status Code、Headers及OperationOutcome。
下一篇將正式開始實作,安裝Postman並送出第一個API Request,實際觀察方法、URL、Headers、Status及Body分別出現在哪裡。
Day 15|安裝Postman並送出第一個API請求
HL7 FHIR R4:RESTful API
https://hl7.org/fhir/R4/http.html
HL7 FHIR R4:Create
https://hl7.org/fhir/R4/http.html#create
HL7 FHIR R4:Update
https://hl7.org/fhir/R4/http.html#update
HL7 FHIR R4:Delete
https://hl7.org/fhir/R4/http.html#delete
HL7 FHIR R4:OperationOutcome
https://hl7.org/fhir/R4/operationoutcome.html
RFC 9110:HTTP Semantics
https://www.rfc-editor.org/rfc/rfc9110